/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-04
 * V4.0
 */
package com.jphenix.kernel.classloader;

import java.io.ByteArrayInputStream;
//#region 【引用区】
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Manifest;

import com.jphenix.kernel.objectloader.util.JarResourceVO;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.ClassInfo;
//#endregion

//#region 【说明区】
/**
 * 用于热替换的类加载器
 * 
 * 2019-01-16 增加了可以加载扩展类路径的方法，并修改了错误
 * 2019-02-11 没干啥，在一个地方加了日志后，又发现不能在那里加日志，又把日志注释掉了
 * 2019-10-30 增加了加载扩展库中readme.txt中描述的引用其它扩展库信息
 * 2019-11-04 修改了，扩展类路径中引用了其它扩展类路径时无效的错误
 * 2020-05-12 方法名addNewSource改为addSource
 * 2022-02-08 加载扩展包后，返回加载的扩展包的详细路径，而不是之前的文件夹名
 *            注释了一些无用的语句
 * 2024-05-21 取消了无用并且复杂不好用的ext_lib扩展库功能，改用 WEB-INF/lib 中创建子文件夹，将第三方包分类保存
 * 2024-05-21 增加了通过同一个路径获取多个资源的方法
 * 2024-08-17 适配了嵌入Spring架构
 * 
 * @author 刘虻
 * 2008-4-10下午05:07:58
 */
//#endregion
@ClassInfo({"2024-08-18 13:04","用于热替换的类加载器"})
public class BeanClassLoader extends URLClassLoader {
	
	//#region 【变量声明区】
	protected ResourcesLoader rl = null; // SDK包中resources资源加载器
	protected ResourceService rs = null; // 包资源服务
	//#endregion

	//#region 【构造函数】
	/**
	 * 构造函数
	 * @param parent 父类加载器
	 * 2008-4-10下午05:07:58
	 */
	public BeanClassLoader(ClassLoader parent,ResourcesLoader rl,ResourceService rs) {
		super(new URL[0],parent);
		this.rl = rl;
		this.rs = rs;
		loadInsideJars(); // 加载SDK包内部的第三方jar包
	}
	//#endregion
	
	//#region loadInsideJars() 加载SDK包内部的第三方jar包
	/**
	 * 加载SDK包内部的第三方jar包
	 * 2024年8月9日
	 * @author MBG
	 */
	private void loadInsideJars() {
		
		String basePath = "resources/lib"; // 压缩包中的根路径
		
		// 获取数据库文件资源序列 key中并没有 /resources 路径，但资源元素是带的
		List<JarResourceVO> jrVOs = rl.getChildSources("lib/");
		for(JarResourceVO vo:jrVOs) {
    		// 添加资源
    		rs.addSource(vo.entrie,vo.jarFile,basePath);
		}
	}
	//#endregion
	
	//#region loadLibChildPath(libPath) 执行加载/WEB-INF/lib 子文件夹中的jar包和zip包
	/**
	 * 执行加载/WEB-INF/lib 子文件夹中的jar包和zip包
	 * @param libPath /WEB-INF/lib 的全路径
	 * @return 加载的类文件
	 * 2018年3月30日
	 * @author MBG
	 */
	public List<String> loadLibChildPath(String libPath) {
		//搜索的文件信息序列
		List<String> fileList = new ArrayList<String>();
		try {
			SFilesUtil.getFileList(fileList,libPath,null,"jar;zip",true,true);
		}catch(Exception e) {
			e.printStackTrace();
			return new ArrayList<String>();
		}
		//构建返回值
		List<String> reList = new ArrayList<String>();
		int point; //分隔符位置
		String path;
		for(String subPath:fileList) {
			point = subPath.lastIndexOf("/");
			if(point<0) {
				//这不是子文件夹中的文件
				continue;
			}
			path = libPath+"/"+subPath;
			reList.add(path);
			rs.addClassPath(path); //加载该包文件
		}
		return reList;
	}
	//#endregion
	
	//#region getResourceAsStream(name) 获取内部资源流
	/**
	 * 获取内部资源流
	 * @param name 资源名
	 * @return     资源读入流
	 */
    public InputStream getResourceAsStream(String name) {
    	// 尝试获取资源对象
    	ResourceEntry re = rs.getSource(name);
    	if(re==null) {
    		return super.getResourceAsStream(name);
    	}
    	if(re.fileBytes!=null) {
    		return new ByteArrayInputStream(re.fileBytes);
    	}
		if(re.inJar) {
			//压缩文件对象
			File fileObj = new File(re.codeBase);
			re.lastModified = fileObj.lastModified();
			String childPath = re.source;
			if(childPath!=null) {
				//从压缩包全路径中分离出子路径
				int point = childPath.indexOf("!");
				if(point>0) {
					childPath = childPath.substring(point+1);
				}
			}
			try {
				return SFilesUtil.getZipFileInputStreamByUrl(childPath,SFilesUtil.getZipFile(fileObj));
			}catch(Exception e) {
				e.printStackTrace();
				return null;
			}
		}else {
			//构建资源文件对象
			File resFile = new File(re.source);
			re.lastModified = resFile.lastModified();
			try {
				return new FileInputStream(resFile);
			}catch(Exception e) {
				e.printStackTrace();
				return null;
			}
		}
    }
    //#endregion
	
	//#region getResources(name) 通过相对路径，获取指定资源URL枚举对象
	/**
	 * 通过相对路径，获取指定资源URL枚举对象
	 */
    public Enumeration<URL> getResources(String name) throws IOException {
		return findResources(name);
    }
	//#endregion
    
    //#region findResources(name) 通过相对路径，获取指定资源URL枚举对象
	/**
	 * 通过相对路径，获取指定资源URL枚举对象
	 */
    public Enumeration<URL> findResources(final String name) throws IOException {
        if(name==null || name.length()<1) {
        	return null;
        }
        // 从全局包资源管理服务中获取资源对象序列
        List<ResourceEntry> res = rs.getSources(name);
        if(res==null) {
        	return super.findResources(name);
        }
    	return new Enumeration<URL>() {
    		private int index = 0;
			@Override
			public boolean hasMoreElements() {
				if(res==null || res.size()<1) {
					return false;
				}
				return index<res.size();
			}
			@Override
			public URL nextElement() {
				if(res==null || res.size()<1 || index>=res.size()) {
					return null;
				}
				ResourceEntry re = res.get(index);
				index++;
				try {
					return re==null?null:re.toUrl();
				}catch(Exception e) {
					e.printStackTrace();
					return null;
				}
			}
    	};
    }
    //#endregion
	
    //#region createClass(name,re) 加载类到缓存
	/**
	 * 加载类到缓存
	 * 刘虻
	 * 2010-8-31 下午01:47:29
	 * @param name 类名
	 * @param re 类资源对象
	 * @throws ClassNotFoundException 执行发生异常
	 */
	@SuppressWarnings("resource")
	protected void createClass(String name,ResourceEntry re) throws ClassNotFoundException {
		if(re.fileBytes!=null) {
			re.setLoadedClass(defineClass(name,re.fileBytes, 0,re.fileBytes.length));
			return;
		}
		InputStream is = null; //类读入流
		if(re.inJar) {
			//压缩文件对象
			String fileObjPath = re.codeBase;
			//#region 如果这个url是1级子文件的（即 /xxx/xxx.jar!xxx/file 只有一个感叹号）
			// 只有一个感叹号的，压缩包内文件路径开头不该有/
			// 但如果大于一个感叹号，只有末级路径开头不带/，其余的路径都带
			int point = fileObjPath.lastIndexOf("!/");
			if(point>0 && fileObjPath.lastIndexOf("!")==point) {
				// 如果末级感叹号位置后面的路径带 / 符号，则去掉该符号
				fileObjPath = fileObjPath.substring(0,point)+"!"+fileObjPath.substring(point+2);
			}
			//#endregion
			File fileObj = new File(fileObjPath);
			re.lastModified = fileObj.lastModified();
			String childPath = re.source;
			if(childPath!=null) {
				//从压缩包全路径中分离出子路径
				point = childPath.indexOf("!");
				if(point>0) {
					childPath = childPath.substring(point+1);
				}
				if(childPath.startsWith("/")) {
					childPath = childPath.substring(1);
				}
			}
			try {
				is = SFilesUtil.getZipFileInputStreamByUrl(childPath,SFilesUtil.getZipFile(fileObj));
			}catch(Exception e) {
				e.printStackTrace();
				throw new ClassNotFoundException(e.toString());
			}
		}else {
			//构建资源文件对象
			File resFile = new File(re.source);
			re.lastModified = resFile.lastModified();
			try {
				is = new FileInputStream(resFile);
			}catch(Exception e) {
				e.printStackTrace();
				throw new ClassNotFoundException(e.toString());
			}
		}
		//输出流
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024]; //缓存
		int bytesRead = -1; //读取数
		try {
			while ((bytesRead = is.read(buffer)) != -1) {
				bos.write(buffer, 0, bytesRead);
			}
		}catch(Exception e) {
			e.printStackTrace();
			throw new ClassNotFoundException(e.toString());
		}finally {
			try {
				is.close();
			}catch (IOException ex) {}
		}
		try {
	        //初始化类
			re.setLoadedClass(defineClass(name, bos.toByteArray(), 0,bos.size()));
		}catch(Exception e) {
			e.printStackTrace();
			throw new ClassNotFoundException(e.toString());
		}
		//构建包对象
		String packageName = name.substring(0,name.lastIndexOf("."));
		if(getPackage(packageName)==null) {
			super.definePackage(packageName,new Manifest(),null);
		}
	}
	//#endregion
	
	//#region findClass(name)
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:54:04
	 */
	@Override
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
    	//通过类路径获取对应的相对路径
    	String classKey  = BaseUtil.swapString(name,".","/")+".class";
    	ResourceEntry re = rs.getSource(classKey); //资源对象
    	if(re==null) {
    		return super.findClass(name);
    	}
    	if(re.getLoadedClass()==null) {
    		createClass(name,re);
    	}else {
	    	//检测是否发生变化
	    	File cTimeFile;
	    	if(re.inJar) {
	    		cTimeFile = new File(re.codeBase);
	    	}else{
	    		cTimeFile = new File(re.source);
	    	}
	    	if(re.lastModified!=cTimeFile.lastModified()) {
	    		//文件发生了变化
	    		createClass(name,re);
	    	}
    	}
    	return re.getLoadedClass();
    }
	//#endregion
	
	//#region getResource(name)
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-9-3 下午07:07:47
	 */
	@Override
    public URL getResource(String name) {
		URL reUrl = findResource(name);
		if(reUrl==null) {
			return super.getResource(name);
		}
		return reUrl;
	}
	//#endregion
	
	//#region findResource(name)
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:52:24
	 */
    @Override
    public URL findResource(final String name) {
		//整理路径
		String fixName = BaseUtil.swapString(name,"\\","/");
		ResourceEntry re = rs.getSource(fixName); //从全局包资源管理服务中获取资源对象
		if(re==null) {
			//不要在这里打日志，因为很多时候是尝试性寻找，不一定非得找到
			//System.err.println("findResource Not Find The Name:["+name+"]  fixName:["+fixName+"]");
			return super.findResource(name);
		}
		try {
			return re.toUrl();
		}catch(Exception e) {
			e.printStackTrace();
		}
		return null;
	}
    //#endregion
	
    //#region getURLs()
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:51:47
	 */
    @Override
    public URL[] getURLs() {
    	return rs.getURLs();
    }
	//#endregion
    
    //#region addURL(url)
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:50:45
	 */
	@Override
    protected void addURL(URL url) {
    	if(url==null) {
    		return;
    	}
    	if("file".equalsIgnoreCase(url.getProtocol())) {
    		//该类只能处理本地文件
    		rs.addClassPath(url.getPath());
    	}else {
    		super.addURL(url);
    	}
    }
	//#endregion
	
	//#region addClassPath(classPath) 添加一个类路径（注意：不支持多个路径用分隔符分割）
	/**
	 * 添加一个类路径（注意：不支持多个路径用分隔符分割）
	 * @param classPath 一个类路径
	 * 2019年1月18日
	 * @author MBG
	 */
	public void addClassPath(String classPath) {
		rs.addClassPath(classPath);
	}
	//#endregion
	
	//#region addSource(basePath,sourceFile) 添加一个资源对象
	/**
	 * 添加一个资源对象
	 * @param basePath    资源根路径
	 * @param sourceFile  资源文件对象
	 * 2019年1月18日
	 * @author MBG
	 */
	public void addSource(String basePath,File sourceFile) {
		rs.addSource(basePath,sourceFile);
	}
	//#endregion
	
	//#region loadClass(name)
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-9-10上午10:00:21
	 */
	@Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
	    if(name==null || name.length()<1) {
	        throw new ClassNotFoundException("The ClassPath Is Null");
	    }
		try {
			return findClass(name);
		}catch(Exception e) {}
		
// 如果该类加载器是一个子类加载器，只负责新构建的类。如果通过这个类加载器加载父类加载器管理的类
// 在第一步findClass方法，就会抛异常，执行到此步
// 如果在此步用findSystemClass获取到的类是一个新的类，而并不是正在使用的类，从而导致类型不匹配出错
//		try {
//			//先判断是否为系统类
//			return super.findSystemClass(name);
//		}catch(Exception e) {}
//		try {
//			//先判断是否为系统类
//			return super.findClass(name);
//		}catch(Exception e) {}
//		try {
//			//先判断是否为系统类
//			return super.findSystemClass(name);
//		}catch(Exception e) {}
		try {
			//在从本地加载器中获取
			return super.loadClass(name);
		}catch(Exception e) {}
		//获取类路径
		URL[] urls = getURLs();
		//构建报错信息
		StringBuffer sbf = new StringBuffer();
		sbf
		.append("\n\nNot Find The ClassPath:["+name+"] ClassLoader:"+this)
		.append("\n==============================URL List ================================== Count:["+urls.length+"]\n");
		if(urls!=null) {
			for(int i=0;i<urls.length;i++) {
				sbf.append(urls[i]).append("\n");
			}
		}
		sbf.append("\n==============================URL List===================================\n");
		
		sbf.append(DebugUtil.getStackTraceString(this));
		throw new ClassNotFoundException("\nNot Find The ClassPath:["+name+"] "+sbf);
	}
	//#endregion
}






